package com.tiny.conf.core.core;

import com.tiny.conf.core.zk.TinyZkClient;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 对zk 的相关操作进行封装
 */
public class TinyConfZkConf {
    private static Logger logger = LoggerFactory.getLogger(TinyConfZkConf.class);

    // ----------------------------- zk client -----------------------------
    private static final String tinyZkBasePath = "/tiny-conf";
    private static String tinyZkEnvPath;

    private static TinyZkClient tinyZkClient = null;

    // 初始化tinyzkclient
    public static void init(String zkaddress, String zkdigest, String env) {

        // valid
        if (zkaddress == null || zkaddress.trim().length() == 0) {
            throw new RuntimeException("xxl-conf zkaddress can not be empty");
        }

        // init zkpath
        if (env == null || env.trim().length() == 0) {
            throw new RuntimeException("xxl-conf env can not be empty");
        }

        //  组装环境路径
        TinyConfZkConf.tinyZkEnvPath = tinyZkBasePath + "/" + env;

        // 初始化tinyzkClint
        tinyZkClient = new TinyZkClient(zkaddress, tinyZkEnvPath, zkdigest, new Watcher() {
            /**
             * Java函数的主要功能如下：
             * 创建Zookeeper客户端：
             *      使用给定的zkaddress、tinyZkEnvPath、zkdigest等参数创建一个TinyZkClient实例。
             * 设置Watcher监听器：
             *      通过Watcher接口实现process方法，监听Zookeeper中的事件。
             * 处理会话过期：
             *      当事件状态为Event.KeeperState.Expired时：
             *      销毁当前的TinyZkClient实例。
             *      重新获取Zookeeper客户端。
             *      重新加载所有本地缓存的配置，并记录成功日志。
             * 监听节点变化：
             *      获取事件对应的路径。
             *      将路径转换为键（pathToKey）。
             *      如果键不为空，则对路径进行存在性检查（保持监听）。
             * 根据事件类型处理不同的情况：
             *      节点删除：记录日志或执行相应操作。
             *      数据变更：获取新的配置数据，并更新本地缓存。
             * 异常处理：
             *      捕获KeeperException和InterruptedException，记录错误信息。
             * @param watchedEvent
             */
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    logger.info(">>>>>>>>>> xxl-conf: watcher:{}", watchedEvent);

                    // session expire, close old and create new
                    if (watchedEvent.getState() == Event.KeeperState.Expired) {
                        tinyZkClient.destroy();
                        tinyZkClient.getClient();


                        TinyConfLocalCacheConf.reloadAll();
                        logger.info(">>>>>>>>>> xxl-conf, zk re-connect reloadAll success.");
                    }
                    //
                    String path = watchedEvent.getPath();

                    String key = pathToKey(path);
                    if (key != null) {
                        // keep watch conf key：add One-time trigger
                        tinyZkClient.getClient().exists(path, true);
                        if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
                            // conf deleted
                        } else if (watchedEvent.getType() == Event.EventType.NodeDataChanged) {
                            // conf updated
                            String data = get(key);
                            TinyConfLocalCacheConf.update(key, data);
                        }
                    }

                } catch (KeeperException e) {
                    logger.error(e.getMessage(), e);
                } catch (InterruptedException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        });
        // init client
        try {
            tinyZkClient.getClient();
            logger.info(">>>>>>>>>> xxl-conf, XxlConfZkConf init success. [env={}]", env);
        } catch (Exception e) {
            logger.info(">>>>>>>>>> xxl-conf, XxlConfZkConf init error, will retry. [env={}]", env);
            logger.error(e.getMessage(), e);
        }
    }

    public static void destroy() {
        if (tinyZkClient != null) {
            tinyZkClient.destroy();
        }
    }


    // ------------------------------ 配置相关操作 ------------------------------

    /**
     * set zk conf
     * 写入zk
     *
     * @param key
     * @param data
     * @return
     */
    public static void set(String key, String data) {
        String path = keyToPath(key);
        tinyZkClient.setPathData(path, data, false);
    }

    /**
     * delete zk conf
     *
     * @param key
     */
    public static void delete(String key) {
        String path = keyToPath(key);
        tinyZkClient.deletePath(path, false);
    }

    /**
     * get zk conf
     *
     * @param key
     * @return
     */
    public static String get(String key) {
        String path = keyToPath(key);
        return tinyZkClient.getPathData(path, true);
    }


    // ------------------------------ key 2 path / genarate key ------------------------------

    /**
     * path 2 key
     *
     * @param nodePath
     * @return ZnodeKey
     */
    public static String pathToKey(String nodePath) {
        if (nodePath == null || nodePath.length() <= tinyZkEnvPath.length() || !nodePath.startsWith(tinyZkEnvPath)) {
            return null;
        }
        return nodePath.substring(tinyZkEnvPath.length() + 1, nodePath.length());
    }

    /**
     * 将key
     * key 2 path
     *
     * @param nodeKey
     * @return znodePath
     */
    public static String keyToPath(String nodeKey) {
        return tinyZkEnvPath + "/" + nodeKey;
    }

}
